﻿using System.Collections.Generic;
using System.Globalization;
using UnityEngine;

namespace Panda.Utils
{
    public class FairRandomizer
    {
        private List<bool> _preAllocatedList = null;
        private int _currentIndex;
        
        private float _probability;
        private int _maxSampleSize;
        
        public const float Threshold = 0.0001f;
        const float OneMinusThreshold = 1f - Threshold;
        private const float ToggleShuffleMethod = 0.5f;

        private static readonly List<int> s_CachedList = new();

        public FairRandomizer(int maxSampleSize = 100)
        {
            _maxSampleSize = maxSampleSize;
        }

        public bool IsNotPossible(float probability)
        {
            if (probability < Threshold)
            {
                return true;
            }

            return false;
        }

        public bool GetNext(float probability)
        {
            if (IsNotPossible(probability))
            {
                return false;
            }

            if (probability > OneMinusThreshold)
            {
                return true;
            }

            if (_preAllocatedList == null || Mathf.Abs(probability - _probability) > Threshold)
            {
                ReSetup(probability);
            }

            if (_currentIndex < _preAllocatedList.Count)
            {
                return _preAllocatedList[_currentIndex++];
            }
            
            _currentIndex = 0;
            if (probability < ToggleShuffleMethod)
            {
                ReSetup(probability);
            }
            else
            {
                Shuffle(_preAllocatedList);
            }

            return _preAllocatedList[_currentIndex++];
        }

        private void ReSetup(float probability)
        {
            _probability = probability;
            if (_preAllocatedList == null)
            {
                _preAllocatedList = new();
            }
            else
            {
                _preAllocatedList.Clear();
            }
            _currentIndex = 0;

            if (IsNotPossible(probability))
            {
                _preAllocatedList.Add(false);
                return;
            }
            
            if (probability > OneMinusThreshold)
            {
                _preAllocatedList.Add(true);
                return;
            }
            
            var decimalPlaces = GetDecimalPlaces(probability);
            var sampleSize = (int)Mathf.Min(Mathf.Pow(10, decimalPlaces), _maxSampleSize);

            int successCount = Mathf.RoundToInt(probability * sampleSize);
            
            var gcd = CalculateGCD(sampleSize, successCount);
            sampleSize /= gcd;
            successCount /= gcd;
            
            // Debug.Log("probability:" + probability + " Sample Size: " + sampleSize + " Success Count: " + successCount + " GCD: " + gcd);
            
            int failureCount = sampleSize - successCount;

            if (probability < ToggleShuffleMethod)
            {
                var largerCount = Mathf.Max(successCount, failureCount);
                var smallerCount = Mathf.Min(successCount, failureCount);

                var sliceCount = largerCount / smallerCount;
                s_CachedList.Clear();
                for (int i = 0; i < smallerCount; i++)
                {
                    s_CachedList.Add(sliceCount);
                }
            
                var modCount = largerCount % smallerCount;
                while (modCount > 0)
                {
                    var randIdx = Random.Range(0, s_CachedList.Count);
                    s_CachedList[randIdx]++;
                    modCount--;
                }
            
                var largerValue = largerCount == successCount ? true : false;
                var smallerValue = !largerValue;

                for (int i1 = 0; i1 < largerCount; i1++)
                {
                    _preAllocatedList.Add(largerValue);
                }

                var endCount = _preAllocatedList.Count;
                for (int i2 = s_CachedList.Count - 1; i2 >= 0; i2--)
                {
                    var itemCount = s_CachedList[i2];
                    var startIndex = endCount - itemCount;
                    var randIdx = Random.Range(startIndex, endCount + 1);
                    _preAllocatedList.Insert(randIdx, smallerValue);

                    endCount = startIndex;
                }
            }
            else
            {
                // 预分配成功和失败
                for (int i = 0; i < successCount; i++)
                {
                    _preAllocatedList.Add(true);
                }
                for (int i = 0; i < failureCount; i++)
                {
                    _preAllocatedList.Add(false);
                }
                
                Shuffle(_preAllocatedList);
            }
        }

        private void Shuffle(List<bool> list)
        {
            for (int i = list.Count - 1; i > 0; i--)
            {
                int j = UnityEngine.Random.Range(0, i + 1);
                (list[i], list[j]) = (list[j], list[i]);
            }
        }

        private static int GetDecimalPlaces(float value)
        {
            // if (float.IsNaN(value)) return 0;
            // if (float.IsInfinity(value)) return 0;
        
            int decimalPlaces = 0;
            float multipliedValue = value;
        
            // Multiply by 10 until we get an integer (within tolerance)
            while (Mathf.Abs(multipliedValue - Mathf.Round(multipliedValue)) > Threshold)
            {
                multipliedValue *= 10f;
                decimalPlaces++;
            
                // Safety check to prevent infinite loops
                if (decimalPlaces > 15) break;
            }
        
            return decimalPlaces;
        }
        
        private int CalculateGCD(int a, int b)
        {
            while (b != 0)
            {
                int temp = b;
                b = a % b;
                a = temp;
            }
            
            return a;
        }
    }
}